package network

import (
	"fmt"
	"github.com/sirupsen/logrus"
	"github.com/vishvananda/netlink"
	"net"
	"os/exec"
	"strings"
	"time"
)

// BridgeNetworkDriver 桥接驱动
type BridgeNetworkDriver struct {
}

// Name 驱动名
func (d *BridgeNetworkDriver) Name() string {
	return "bridge"
}

// Create 创建网络接口
func (d *BridgeNetworkDriver) Create(subnet string, name string) (*Network, error) {
	ip, ipNet, _ := net.ParseCIDR(subnet)
	ipNet.IP = ip
	n := &Network{
		Name:    name,
		IpRange: ipNet,
		Driver:  d.Name(),
	}
	// 配置 Linux Bridge
	err := d.initBridge(n)
	if err != nil {
		logrus.Errorf("Failed to create bridge network: %v", err)
		return nil, err
	}
	return n, nil
}

// Delete 删除网络
func (d *BridgeNetworkDriver) Delete(network *Network) error {
	// 清除路由规则
	err := deleteIPRoute(network.Name, network.IpRange.String())
	if err != nil {
		logrus.Errorf("clean route rule failed after bridge [%s] deleted, err: %v", network.Name, err)
		return err
	}
	// 清除 iptables 规则
	err = deleteIPTables(network.Name, network.IpRange)
	if err != nil {
		logrus.Errorf("clean snat iptables rule failed after bridge [%s] deleted, err: %v", network.Name, err)
	}
	// 删除网桥
	err = d.deleteBridge(network)
	if err != nil {
		logrus.Errorf("delete bridge [%s] failed, err: %v", network.Name, err)
		return err
	}
	return nil
}

// Connect 连接容器网络端点到网络（创建一个新的虚拟以太网设备(Veth)，并将其添加到网络中）
// 内部会修改 endpoint.Device 必须传指针
func (d *BridgeNetworkDriver) Connect(networkName string, endpoint *Endpoint) error {
	bridgeName := networkName
	// 通过接口名获取到 Linux Bridge 接口的对象和接口属性
	br, err := netlink.LinkByName(bridgeName)
	if err != nil {
		return err
	}
	// 创建 Veth 接口的配置
	la := netlink.NewLinkAttrs() // 创建新的链接属性，用于设置端点的属性。链接属性通常用于定义网络接口的各种属性和配置参数
	// 由于 Linux 接口名的限制，取 endpointID 的前 5 位
	la.Name = endpoint.ID[:5] // 设置端点名称为 ID 的前 5 个字符
	// 通过设置 Veth 接口 master 属性，设置这个 Veth 的一端挂载到网络对应的 Linux Bridge
	la.MasterIndex = br.Attrs().Index
	// 创建 Veth 对象，通过 PeerName 配置 Veth 另外一端的接口名
	// 配置 Veth 另外一端的名字 cif-{endpoint ID 的前 5 位}
	endpoint.Device = netlink.Veth{ // 创建一个 Veth（虚拟以太网对）设备
		LinkAttrs: la,                       // 设置端点的属性
		PeerName:  "cif-" + endpoint.ID[:5], // 设置端点的对端名称
	}
	// 调用 netlink 的 LinkAdd 方法创建出这个 Veth 接口
	// 因为上面指定了 link 的 MasterIndex 是网络对应的 Linux Bridge
	// 所以 Veth 的一端就已经挂载到了网络对应的 Linux Bridge 上
	if err = netlink.LinkAdd(&endpoint.Device); err != nil { // 添加端点设备到网络中
		logrus.Errorf("add endpoint device, err: %v", err)
		return err
	}
	// 调用 netlink 的 LinkSetup 方法，设置 Veth 启动
	// 相当于 ip link set xxx up 命令
	if err = netlink.LinkSetUp(&endpoint.Device); err != nil { // 将端点设备设置为 UP 状态（激活）
		logrus.Errorf("add endpoint device, err: %v", err)
		return err
	}
	return nil
}

func (d *BridgeNetworkDriver) Disconnect(endpointID string) error {
	// 根据名字找到对应的 Veth 设备
	vethName := endpointID[:5] // 由于 Linux 接口名的限制，取 endpointID 的前 5 位
	veth, err := netlink.LinkByName(vethName)
	if err != nil {
		return err
	}
	// 从网桥解绑
	err = netlink.LinkSetNoMaster(veth)
	if err != nil {
		logrus.Errorf("find veth [%s] failed, err: %v", vethName, err)
		return err
	}
	// 删除 veth-pair
	// 一端为 xxx，另一端为 cif-xxx
	err = netlink.LinkDel(veth)
	if err != nil {
		logrus.Errorf("delete veth [%s] failed, err: %v", vethName, err)
		return err
	}
	veth2Name := "cif-" + vethName
	veth2, err := netlink.LinkByName(veth2Name)
	if err != nil {
		logrus.Errorf("find veth2 [%s] failed, err: %v", vethName, err)
		return err
	}
	err = netlink.LinkDel(veth2)
	if err != nil {
		logrus.Errorf("delete veth2 [%s] failed, err: %v", veth2Name, err)
	}
	return nil
}

// initBridge 初始化 Linux Bridge
/*
Linux Bridge 初始化流程如下：
1）创建 Bridge 虚拟设备
2）设置 Bridge 设备地址和路由
3）启动 Bridge 设备
4）设置 iptables SNAT 规则
*/
func (d *BridgeNetworkDriver) initBridge(n *Network) error {
	bridgeName := n.Name
	// 1.创建 Bridge 虚拟设备
	if err := createBridgeInterface(bridgeName); err != nil {
		logrus.Errorf("Failed to create bridge %s, err: %s", bridgeName, err)
		return err
	}

	// 2.设置 Bridge 设备地址和路由
	gatewayIP := *n.IpRange
	gatewayIP.IP = n.IpRange.IP

	// 为网络接口设置 IP 地址
	if err := setInterfaceIP(bridgeName, gatewayIP.String()); err != nil {
		logrus.Errorf("assigning address: %s on bridge: %s with an error: %v", gatewayIP.String(), bridgeName, err)
		return err
	}

	// 3.启动 Bridge 设备
	if err := setInterfaceUP(bridgeName); err != nil {
		logrus.Errorf("set bridge up: %s, err: %v", bridgeName, err)
		return err
	}

	// 4.设置 iptables SNAT 规则
	if err := setupIPTables(bridgeName, n.IpRange); err != nil {
		logrus.Errorf("setting iptables for %s, err: %v", bridgeName, err)
		return err
	}
	return nil
}

func createBridgeInterface(bridgeName string) error {
	_, err := net.InterfaceByName(bridgeName) // 返回指定名字的网络接口
	if err == nil || !strings.Contains(err.Error(), "no such network interface") {
		return err
	}
	// 创建 *netlink.Bridge 对象
	la := netlink.NewLinkAttrs()
	la.Name = bridgeName

	br := &netlink.Bridge{LinkAttrs: la}
	if err = netlink.LinkAdd(br); err != nil {
		logrus.Errorf("bridge create failed for bridge: %s, err: %v", bridgeName, err)
		return err
	}
	return nil
}

// setInterfaceIP 为网络接口设置 IP 地址。ip addr add xxx命令
func setInterfaceIP(name string, rawIP string) error {
	retries := 2 // 可以重试两次
	var iface netlink.Link
	var err error
	for i := 0; i < retries; i++ {
		// 通过 LinkName 方法找到需要设置的网络接口
		iface, err = netlink.LinkByName(name)
		if err == nil {
			break
		}
		logrus.Debugf("error retrieving new bridge netlink link [ %s ]... retrying", name)
		// 在某些情况下，网络接口可能由于各种原因（如，刚被创建或正在被初始化）暂时不可用。
		// 通过暂停几秒钟，程序给予系统足够的时间来确保网络接口完全可用。
		time.Sleep(2 * time.Second)
	}
	if err != nil {
		logrus.Errorf("abandoning retrieving new bridge link from netlink, Run [ ip link ] to troubleshoot the error: %v", err)
		return err
	}
	// 由于 netlink.ParseIPNet 是对 net.ParseCIDR 一个封装，因此可以将 net.ParseCIDO 中返回的 IP 进行整合
	// 返回值中的 ipNet 既包含了网段的信息，192.168.0.0/24，也包含了原始的 IP 192.168.0.0
	ipNet, err := netlink.ParseIPNet(rawIP)
	if err != nil {
		return err
	}
	// 通过 netLink.AddrAdd 给网络接口配置地址，相当于 ip addr add xxx 命令
	// 同时如果配置了地址所在网段的信息，例如 192.168.0/24
	// 还会配置路由表 192.168.0.0/24 转发到这 testbridge 的网络接口上
	addr := &netlink.Addr{IPNet: ipNet}
	return netlink.AddrAdd(iface, addr)
}

// setInterfaceUP 启动 Bridge 设备
// 等价于 ip link set xxx up 命令
func setInterfaceUP(interfaceName string) error {
	iface, err := netlink.LinkByName(interfaceName)
	if err != nil {
		logrus.Errorf("retrieving a link, err: %v", err)
		return err
	}
	// 等价于 ip link set xxx up 命令
	if err = netlink.LinkSetUp(iface); err != nil {
		logrus.Errorf("enabling interface for %s, err: %v", interfaceName, err)
		return err
	}
	return nil
}

// setupIPTables 设置 iptables 对应 bridge MASQUERADE 规则
// iptables -t nat -A POSTROUTING -s 172.18.0.0/24 -o eth0 -j MASQUERADE
// iptables -t nat -A POSTROUTING -s {subnet} -o {driverName} -j MASQUERADE
func setupIPTables(bridgeName string, subnet *net.IPNet) error {
	return configIPTables(bridgeName, subnet, false)
}

func configIPTables(bridgeName string, subnet *net.IPNet, isDelete bool) error {
	action := "-A"
	if isDelete {
		action = "-D"
	}
	// 拼接命令
	iptablesCmd := fmt.Sprintf("-t nat %s POSTROUTING -s %s ! -o %s -j MASQUERADE", action, subnet.String(), bridgeName)
	cmd := exec.Command("iptables", strings.Split(iptablesCmd, " ")...)
	logrus.Infof("配置 SNAT cmd: %v", cmd.String())
	// 执行该命令
	output, err := cmd.Output()
	if err != nil {
		logrus.Errorf("iptables output: %v, err: %v", output, err)
		return err
	}
	return nil
}

func (d *BridgeNetworkDriver) deleteBridge(n *Network) error {
	bridgeName := n.Name

	// 获取 link
	l, err := netlink.LinkByName(bridgeName)
	if err != nil {
		logrus.Errorf("get link with name %s failed: %v", bridgeName, err)
		return err
	}

	// 删除 link
	if err = netlink.LinkDel(l); err != nil {
		logrus.Errorf("remove bridge interface %s, err: %v", bridgeName, err)
		return err
	}
	return nil
}

// deleteIPRoute 删除路由：ip addr del xxx 命令
func deleteIPRoute(name string, rawIP string) error {
	retries := 2
	var iface netlink.Link
	var err error
	for i := 0; i < retries; i++ {
		// 通过 LinkByName 的方法找到需要设置的网络接口
		iface, err = netlink.LinkByName(name)
		if err == nil {
			break
		}
		logrus.Debugf("error retrieving new bridge netlink link [%s]... retrying", name)
		time.Sleep(2 * time.Second)
	}
	if err != nil {
		logrus.Errorf("abandoning retrieving the new bridge link from netlink, Run [ip link] to troubleshoot")
		return err
	}
	// 查询对应设备的路由并全部删除
	list, err := netlink.RouteList(iface, netlink.FAMILY_V4)
	if err != nil {
		return err
	}
	for _, route := range list {
		if route.Dst.String() == rawIP { // 根据子网进行匹配
			err = netlink.RouteDel(&route)
			if err != nil {
				logrus.Errorf("route [%v] del failed, detail: %v", route, err)
				continue
			}
		}
	}
	return nil
}

func deleteIPTables(bridgeName string, subnet *net.IPNet) error {
	return configIPTables(bridgeName, subnet, true)
}
